package modelx

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/volatiletech/sqlboiler/v4/queries/qm"
	"github.com/volatiletech/sqlboiler/v4/queries/qmhelper"
)

const (
	FieldSeparator      = ","
	keyOpSeparator      = ","
	ValueSeparator      = ","
	KeyValueSeparator   = ":"
	RepositorySeparator = ";"
	KeySeparator        = ";"
)

func GetPreloadMap(preload string, eagerLoad string) (map[string][]string, map[string][]string) {
	eagerLoadMap := make(map[string][]string)
	preloadMap := make(map[string][]string)

	if len(eagerLoad) > 0 {
		parts := strings.Split(eagerLoad, RepositorySeparator)
		for _, part := range parts {
			pairs := strings.SplitN(part, KeyValueSeparator, 2)
			if len(pairs) == 2 && len(pairs[0]) > 0 && len(pairs[1]) > 0 {
				if _, ok := preloadMap[pairs[0]]; !ok {
					preloadMap[pairs[0]] = []string{}
				}

				reps := strings.Split(pairs[1], FieldSeparator)
				if len(reps) > 0 {
					for _, rep := range reps {
						if len(rep) > 0 {
							eagerLoadMap[pairs[0]] = append(eagerLoadMap[pairs[0]], rep)

							if _, ok := preloadMap[rep]; !ok {
								preloadMap[rep] = []string{}
							}
						}
					}
				}
			}
		}
	}

	if len(preload) > 0 {
		parts := strings.Split(preload, RepositorySeparator)
		for _, part := range parts {
			pairs := strings.SplitN(part, KeyValueSeparator, 2)
			if len(pairs) == 2 && len(pairs[0]) > 0 && len(pairs[1]) > 0 {
				fields := strings.Split(pairs[1], FieldSeparator)
				if len(fields) > 0 {
					if _, ok := preloadMap[pairs[0]]; !ok {
						preloadMap[pairs[0]] = fields
					} else {
						preloadMap[pairs[0]] = append(preloadMap[pairs[0]], fields...)
					}
				}
			}
		}
	}

	return preloadMap, eagerLoadMap
}

func ParsePreloadAndEagerLoad(preload *string, eagerLoad *string) (map[string][]string, map[string][]string) { // #24.02.29#
	eagerLoadMap := make(map[string][]string)
	preloadMap := make(map[string][]string)

	if eagerLoad != nil && len(*eagerLoad) > 0 {
		parts := strings.Split(*eagerLoad, RepositorySeparator)
		for _, part := range parts {
			pairs := strings.SplitN(part, KeyValueSeparator, 2)
			if len(pairs) == 2 && len(pairs[0]) > 0 && len(pairs[1]) > 0 {
				if _, ok := preloadMap[pairs[0]]; !ok {
					preloadMap[pairs[0]] = []string{}
				}

				reps := strings.Split(pairs[1], FieldSeparator)
				if len(reps) > 0 {
					for _, rep := range reps {
						if len(rep) > 0 {
							eagerLoadMap[pairs[0]] = append(eagerLoadMap[pairs[0]], rep)

							if _, ok := preloadMap[rep]; !ok {
								preloadMap[rep] = []string{}
							}
						}
					}
				}
			}
		}
	}

	if preload != nil && len(*preload) > 0 {
		parts := strings.Split(*preload, RepositorySeparator)
		for _, part := range parts {
			pairs := strings.SplitN(part, KeyValueSeparator, 2)
			if len(pairs) == 2 && len(pairs[0]) > 0 && len(pairs[1]) > 0 {
				fields := strings.Split(pairs[1], FieldSeparator)
				if len(fields) > 0 {
					if _, ok := preloadMap[pairs[0]]; !ok {
						preloadMap[pairs[0]] = fields
					} else {
						preloadMap[pairs[0]] = append(preloadMap[pairs[0]], fields...)
					}
				}
			}
		}
	}

	return preloadMap, eagerLoadMap
}

func ParseSelectColumns(preloadFields []string, columnMap map[string]string) (selectCols []string) { // #24.02.29#
	if len(preloadFields) > 0 && columnMap != nil {
		selectColMap := make(map[string]string)
		for _, field := range preloadFields {
			if _, ok := selectColMap[field]; !ok {
				if column, hasColumn := columnMap[field]; hasColumn {
					selectColMap[field] = column
				} else {
					selectColMap[field] = ""
				}
			}
		}
		for _, column := range selectColMap {
			if len(column) > 0 {
				selectCols = append(selectCols, column)
			}
		}
	}

	return
}

const (
	in          = " IN ?"
	notIn       = " NOT IN ?"
	isLike      = " LIKE ?"
	percentSign = `%`
)

func startsWithValue(v any) string { // #24.02.24#
	return fmt.Sprintf("%v%s", v, percentSign)
}

func endsWithValue(v any) string { // #24.02.24#
	return fmt.Sprintf("%s%v", percentSign, v)
}

func containsValue(v any) string { // #24.02.24#
	return fmt.Sprintf("%s%v%s", percentSign, v, percentSign)
}

func StringsToInterfaces(sa []string) []any { // #24.02.29# []string => []any
	interfaces := make([]any, len(sa))
	for idx, v := range sa {
		interfaces[idx] = v
	}

	return interfaces
}

func IntsToInterfaces(ints []int) []any { // []int => []any
	interfaces := make([]any, len(ints))
	for idx, number := range ints {
		interfaces[idx] = number
	}

	return interfaces
}

func GetFilterMap(filter string, columnMap map[string]string) map[string]map[string][]any { ///////
	if filter == "" { // key1,op1:val1,val2;key1,op2:val3,val4
		return nil
	}

	res := make(map[string]map[string][]any)

	pairs := strings.Split(filter, KeySeparator)
	if len(pairs) > 0 {
		for _, pair := range pairs {
			parts := strings.SplitN(pair, KeyValueSeparator, 2)
			if len(parts) == 2 && len(parts[0]) > 0 && len(parts[1]) > 0 {
				keys := strings.SplitN(parts[0], keyOpSeparator, 2)
				if len(keys) == 2 && keys[0] != "" && keys[1] != "" {
					values := strings.Split(parts[1], ValueSeparator)
					if len(values) > 0 {
						if column, hasColumn := columnMap[keys[0]]; hasColumn {
							if _, exist := res[column]; !exist {
								res[column] = make(map[string][]any)
							}
							res[column][keys[1]] = append(res[column][keys[1]], StringsToInterfaces(values)...)
						}
					}
				}
			}
		}
	}

	return res
}

func ParseFilter(filter *string, columnMap map[string]string) map[string]map[string][]any { // #24.07.11#
	if filter == nil || len(*filter) == 0 { // key1,op1:val1,val2;key1,op2:val3,val4
		return nil
	}

	res := make(map[string]map[string][]any)

	pairs := strings.Split(*filter, KeySeparator)
	if len(pairs) > 0 {
		for _, pair := range pairs {
			parts := strings.SplitN(pair, KeyValueSeparator, 2)
			if len(parts) == 2 && len(parts[0]) > 0 && len(parts[1]) > 0 {
				keys := strings.Split(parts[0], keyOpSeparator)
				if len(keys[0]) > 0 && len(keys[1]) > 0 {
					values := strings.Split(parts[1], ValueSeparator)
					if len(values) > 0 {
						if column, ok := columnMap[keys[0]]; ok {
							if _, ok := res[column]; !ok {
								res[column] = make(map[string][]any)
							}
							res[column][keys[1]] = append(res[column][keys[1]], StringsToInterfaces(values)...)
						}
					}
				}
			}
		}
	}

	return res
}

func ParseMetaFilter(filter *string, columnMap map[string]string, entitySettingMap map[string]string) map[string]map[string][]any { // #24.07.11# 完成
	if filter == nil || len(*filter) == 0 { // key1,op1:val1,val2
		return nil
	}

	res := make(map[string]map[string][]any)
	parts := strings.SplitN(*filter, KeyValueSeparator, 2)
	if len(parts) == 2 && len(parts[0]) > 0 && len(parts[1]) > 0 {
		keys := strings.Split(parts[0], keyOpSeparator)
		if len(keys[0]) > 0 && len(keys[1]) > 0 {
			values := strings.Split(parts[1], ValueSeparator)
			if len(values) > 1 {
				if fieldName, ok := entitySettingMap[keys[0]]; ok && len(fieldName) > 0 {
					metaKey := columnMap["MetaKey"]
					metaValue := columnMap["MetaValue"]
					res[metaKey] = make(map[string][]any)
					res[metaKey][keys[1]] = append(res[metaKey][keys[1]], fieldName)
					res[metaValue] = make(map[string][]any)
					res[metaValue][values[0]] = append(res[metaValue][values[0]], StringsToInterfaces(values[1:])...)
				}
			}
		}
	}

	return res
}

func GetOrderBy(sorter string, columnMap map[string]string) string {
	if len(sorter) == 0 { // key1:val1;key2:val2
		return ""
	}

	res := make(map[string]string)
	pairs := strings.Split(sorter, KeySeparator)
	if len(pairs) > 0 {
		for _, pair := range pairs {
			parts := strings.SplitN(pair, KeyValueSeparator, 2)
			if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
				if column, hasColumn := columnMap[parts[0]]; hasColumn {
					if _, exist := res[column]; !exist {
						res[column] = parts[1]
					}
				}
			} else if len(parts) == 1 && parts[0] != "" {
				if column, hasColumn := columnMap[parts[0]]; hasColumn {
					if _, exist := res[column]; !exist {
						res[column] = "0"
					}
				}
			}
		}
	}

	var sa []string
	for key, val := range res {
		var order string = " ASC"
		if val == "1" {
			order = " DESC"
		}
		sa = append(sa, key+order)
	}

	return strings.Join(sa, ",")
}

func ParseSorter(sorter *string, columnMap map[string]string) string {
	if sorter == nil || len(*sorter) == 0 { // key1:val1;key2:val2
		return ""
	}

	sm := make(map[string]string)
	pairs := strings.Split(*sorter, KeySeparator)
	if len(pairs) > 0 {
		for _, pair := range pairs {
			parts := strings.SplitN(pair, KeyValueSeparator, 2)
			if len(parts) == 2 && len(parts[0]) > 0 && len(parts[1]) > 0 {
				if column, hasColumn := columnMap[parts[0]]; hasColumn {
					if _, ok := sm[column]; !ok {
						sm[column] = parts[1]
					}
				}
			} else if len(parts[0]) > 0 {
				if column, hasColumn := columnMap[parts[0]]; hasColumn {
					if _, ok := sm[column]; !ok {
						sm[column] = "0"
					}
				}
			}
		}
	}

	var sa []string
	for key, val := range sm {
		var order string = " ASC"
		if val == "1" {
			order = " DESC"
		}
		sa = append(sa, key+order)
	}

	return strings.Join(sa, ",")
}

func ParseCount(count int64) (hasNextPage uint32, hasPreviousPage uint32) { // #24.07.11# 完成
	if count > 1 {
		hasNextPage = 1
		count = count - 2
	}
	if count > 0 {
		hasPreviousPage = 1
	}

	return
}

func Int64sToInterfaces(ints []int64) []any { // []int64 => []any
	interfaces := make([]any, len(ints))
	for idx, number := range ints {
		interfaces[idx] = number
	}

	return interfaces
}

func GetIntMods(column string, opMap map[string][]any) (res []qm.QueryMod) { // #24.02.24#
	if len(column) == 0 || opMap == nil {
		return
	}

	for op, vals := range opMap {
		if len(vals) > 0 {
			switch op {
			case "equalTo":
				res = append(res, qmhelper.Where(column, qmhelper.EQ, vals[0]))
			case "notEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.NEQ, vals[0]))
			case "lessThan":
				res = append(res, qmhelper.Where(column, qmhelper.LT, vals[0]))
			case "moreThan":
				res = append(res, qmhelper.Where(column, qmhelper.GT, vals[0]))
			case "lessThanOrEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.LTE, vals[0]))
			case "moreThanOrEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.GTE, vals[0]))
			case "in":
				res = append(res, qm.WhereIn(column+in, vals...))
			case "notIn":
				res = append(res, qm.WhereIn(column+notIn, vals...))
			}
		}
	}

	return
}

func GetBooleanMods(column string, opMap map[string][]any) (res []qm.QueryMod) { // #24.02.24#
	if len(column) == 0 || opMap == nil {
		return
	}

	for op, vals := range opMap {
		if len(vals) > 0 {
			switch op {
			case "equalTo":
				res = append(res, qmhelper.Where(column, qmhelper.EQ, vals[0]))
			case "notEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.NEQ, vals[0]))
			}
		}
	}

	return
}

func GetStringMods(column string, opMap map[string][]any) (res []qm.QueryMod) { // #24.02.24#
	if len(column) == 0 || opMap == nil {
		return
	}

	lowerColumn := "LOWER(" + column + ")"
	for op, vals := range opMap {
		if len(vals) > 0 {
			switch op {
			case "equalTo":
				res = append(res, qmhelper.Where(column, qmhelper.EQ, vals[0]))
			case "notEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.NEQ, vals[0]))
			case "in":
				res = append(res, qm.WhereIn(column+in, vals...))
			case "notIn":
				res = append(res, qm.WhereIn(column+notIn, vals...))
			case "startWith":
				res = append(res, qm.Where(lowerColumn+isLike, strings.ToLower(startsWithValue(vals[0]))))
			case "endWith":
				res = append(res, qm.Where(lowerColumn+isLike, strings.ToLower(endsWithValue(vals[0]))))
			case "contain":
				res = append(res, qm.Where(lowerColumn+isLike, strings.ToLower(containsValue(vals[0]))))
			case "startWithStrict":
				res = append(res, qm.Where(column+isLike, startsWithValue(vals[0])))
			case "endWithStrict":
				res = append(res, qm.Where(column+isLike, endsWithValue(vals[0])))
			case "containStrict":
				res = append(res, qm.Where(column+isLike, containsValue(vals[0])))
			}
		}
	}

	return
}

func GetTimeMods(column string, opMap map[string][]any) (res []qm.QueryMod) { // #24.02.24#
	if len(column) == 0 || opMap == nil {
		return
	}

	for op, vals := range opMap {
		if len(vals) > 0 {
			switch op {
			case "equalTo":
				res = append(res, qmhelper.Where(column, qmhelper.EQ, vals[0]))
			case "notEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.NEQ, vals[0]))
			case "lessThan":
				res = append(res, qmhelper.Where(column, qmhelper.LT, vals[0]))
			case "moreThan":
				res = append(res, qmhelper.Where(column, qmhelper.GT, vals[0]))
			case "lessThanOrEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.LTE, vals[0]))
			case "moreThanOrEqualTo":
				res = append(res, qmhelper.Where(column, qmhelper.GTE, vals[0]))
			}
		}
	}

	return
}

func RemoveHtmlTag(rawHtml string) (string, error) {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(rawHtml))
	if err != nil {
		return "", err
	}

	htmlString := doc.Text()
	return htmlString, nil
}
